<RouterLink>
導到 /projects/:slug
useRoute()
讀取 :slug
,從本地資料查單筆內容/projects/:slug/info
、/projects/:slug/gallery
npm i vue-router@4
建立 src/router/index.ts
:
// src/router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '@/views/Home.vue'
import ProjectDetail from '@/views/ProjectDetail.vue'
import NotFound from '@/views/NotFound.vue'
const routes: RouteRecordRaw[] = [
{ path: '/', name: 'home', component: Home },
{ path: '/projects/:slug', name: 'project-detail', component: ProjectDetail, props: true },
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound }
]
export const router = createRouter({
history: createWebHistory(), // 若部署在子目錄,改 createWebHistory('/子路徑/')
routes,
scrollBehavior() {
return { top: 0 }
}
})
把 router 掛到 app(修改 src/main.ts
):
import { createApp } from 'vue'
import App from './App.vue'
import { router } from './router'
import './styles/base.css'
createApp(App).use(router).mount('#app')
建立 src/views/Home.vue
,把原本 App.vue
內主要內容移到這裡(Header/Footer 繼續在 App.vue
):
<!-- src/views/Home.vue -->
<template>
<main id="home">
<Hero />
<About />
<Skills />
<Projects />
<Contact />
</main>
</template>
<script setup lang="ts">
import Hero from '@/components/Hero.vue'
import About from '@/components/About.vue'
import Skills from '@/components/Skills.vue'
import Projects from '@/components/Projects.vue'
import Contact from '@/components/Contact.vue'
</script>
把 App.vue
改為只負責框架與 router 入口:
<!-- src/App.vue -->
<template>
<SiteHeader />
<RouterView />
<SiteFooter />
</template>
<script setup lang="ts">
import { RouterView } from 'vue-router'
import SiteHeader from '@/components/SiteHeader.vue'
import SiteFooter from '@/components/SiteFooter.vue'
</script>
<RouterLink>
導向詳情頁修改 src/components/Projects.vue
:把 demo / repo 留著,同時加一個「查看詳情」連到 /projects/:slug
。
<!-- 片段:src/components/Projects.vue -->
<article class="card" v-for="p in view" :key="p.id">
<h3>{{ p.title }}</h3>
<p class="muted">{{ p.tech }}</p>
<p>{{ p.desc }}</p>
<div class="actions" style="display:flex; gap:8px; margin-top:8px;">
<RouterLink class="btn small" :to="{ name:'project-detail', params:{ slug: p.slug } }">
查看詳情
</RouterLink>
<a class="btn small btn-outline" :href="p.repo" target="_blank" rel="noopener">GitHub</a>
</div>
</article>
為了 SSR / SEO 的一致性,建議只把「站內導覽」用 ,外部連結維持 。
slug
並顯示單筆專案建立 src/views/ProjectDetail.vue
:
<template>
<section class="container section" v-if="project">
<nav style="margin-bottom:12px;">
<RouterLink to="/" class="btn btn-outline">← 返回列表</RouterLink>
</nav>
<h2>{{ project.title }}</h2>
<p class="muted">{{ project.tech }}</p>
<div class="gallery" v-if="project.images?.length" style="display:flex; gap:12px; flex-wrap:wrap; margin:12px 0;">
<img v-for="src in project.images" :key="src" :src="src" alt="專案截圖" width="360" />
</div>
<p>{{ project.desc }}</p>
<div class="actions" style="display:flex; gap:8px; margin-top:8px;">
<a class="btn" :href="project.demo" target="_blank" rel="noopener">Live Demo</a>
<a class="btn btn-outline" :href="project.repo" target="_blank" rel="noopener">GitHub</a>
</div>
</section>
<section class="container section" v-else>
<h2>找不到這個專案</h2>
<p class="muted">請回到列表,或確認網址是否正確。</p>
<RouterLink to="/" class="btn">返回列表</RouterLink>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { projects } from '@/data/projects'
const route = useRoute()
const slug = computed(() => String(route.params.slug || ''))
const project = computed(() => projects.find(p => p.slug === slug.value))
</script>
新增 src/views/NotFound.vue
:
<template>
<section class="container section">
<h2>404 找不到頁面</h2>
<p class="muted">你要找的頁面不存在,或已被移動。</p>
<RouterLink to="/" class="btn">回首頁</RouterLink>
</section>
</template>
路由中已加入 { path: '/:pathMatch(.)', component: NotFound }。
/projects/:slug/info
、/projects/:slug/gallery
如果想把詳情切成分頁(資訊/圖片):
src/views/ProjectInfo.vue
、src/views/ProjectGallery.vue
(可重用上面 ProjectDetail 裡的片段)src/router/index.ts
):import ProjectInfo from '@/views/ProjectInfo.vue'
import ProjectGallery from '@/views/ProjectGallery.vue'
{
path: '/projects/:slug',
component: ProjectDetail, // 當父頁,內含次級 <RouterView />
props: true,
children: [
{ path: '', redirect: { name: 'project-info' } },
{ path: 'info', name: 'project-info', component: ProjectInfo, props: true },
{ path: 'gallery', name: 'project-gallery', component: ProjectGallery, props: true }
]
}
在 ProjectDetail.vue
中加入子導航與 <RouterView />
:
<nav class="sub-nav" style="display:flex; gap:12px; margin:12px 0;">
<RouterLink :to="{ name:'project-info', params:{ slug } }" active-class="active">資訊</RouterLink>
<RouterLink :to="{ name:'project-gallery', params:{ slug } }" active-class="active">圖片</RouterLink>
</nav>
<RouterView />
/
顯示 Home(含 Projects 列表)/projects/:slug
看到該專案詳細資料info / gallery
index.html
index.html
放頁面views/
,由 Router 管<a href="/xxx">
做站內導覽
<RouterLink :to="...">
(SPA 體驗、保留狀態)createApp(App).mount('#app')
createApp(App).use(router).mount('#app')
assets
路徑;Vite 會把 src/assets
內的資源處理成相對路徑。表單與驗證(Vue 版):
v-model
+ 自訂驗證 改良 Contact 表單(即時錯誤、送出前檢查)